package com.jfinal.weixin.sdk.kit;

import java.io.IOException;
import java.io.StringReader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.jfinal.weixin.sdk.api.ApiConfig;
import com.jfinal.weixin.sdk.api.ApiConfigKit;
import com.jfinal.weixin.sdk.encrypt.AesException;
import com.jfinal.weixin.sdk.encrypt.WXBizMsgCrypt;
import com.jfinal.wxaapp.WxaConfig;
import com.jfinal.wxaapp.WxaConfigKit;

/**
 * 对微信平台官方给出的加密解析代码进行再次封装
 *
 * 异常java.security.InvalidKeyException:illegal Key Size的解决方案：
 * 1：在官方网站下载JCE无限制权限策略文件（JDK7的下载地址：
 *     http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
 * 2：下载后解压，可以看到local_policy.jar和US_export_policy.jar以及readme.txt
 * 3：如果安装了JRE，将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
 * 4：如果安装了JDK，将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
 *
 *
 * 设置为消息加密模式后 JFinal Action Report 中有如下参数：
 * timestamp=1417610658
 * encrypt_type=aes
 * nonce=132155339
 * msg_signature=8ed2a14146c924153743162ab2c0d28eaf30a323
 * signature=1a3fad9a528869b1a73faf4c8054b7eeda2463d3
 */
public class MsgEncryptKit {

    private static final String format = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>";
    /**
     * 加密公众号消息
     * @param msg
     * @param timestamp
     * @param nonce
     * @return
     */
    public static String encrypt(String msg, String timestamp, String nonce) {
    	try {
    		ApiConfig ac = ApiConfigKit.getApiConfig();
    		return encryptMsg(msg, timestamp, nonce, ac.getAppId(), ac.getToken(), ac.getEncodingAesKey());
    	}
    	catch (Exception e) {
    		throw new RuntimeException(e);
    	}
    }
	/**
	 * 加密小程序消息
	 * @param msg
	 * @param timestamp
	 * @param nonce
	 * @return
	 */
	 public static String encryptWxaMsg(String msg, String timestamp, String nonce) {
	    	try {
	    		WxaConfig wxac = WxaConfigKit.getWxaConfig();
	    		return encryptMsg(msg, timestamp, nonce, wxac.getAppId(), wxac.getToken(), wxac.getEncodingAesKey());
	    	}
	    	catch (Exception e) {
	    		throw new RuntimeException(e);
	    	}
	    }
    /**
          * 解密公众号消息
     * @param encryptedMsg
     * @param timestamp
     * @param nonce
     * @param msgSignature
     * @return
     */
    public static String decrypt(String encryptedMsg, String timestamp, String nonce, String msgSignature) {
        try {
        	ApiConfig ac = ApiConfigKit.getApiConfig();
            return decryptMsg(encryptedMsg, timestamp, nonce, msgSignature, ac.getAppId(), ac.getToken(), ac.getEncodingAesKey());
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
          * 解密小程序客服消息
     * @param encryptedMsg
     * @param timestamp
     * @param nonce
     * @param msgSignature
     * @return
     */
    public static String decryptWxaMsg(String encryptedMsg, String timestamp, String nonce, String msgSignature) {
        try {
        	WxaConfig wxac = WxaConfigKit.getWxaConfig();
            return decryptMsg(encryptedMsg, timestamp, nonce, msgSignature, wxac.getAppId(), wxac.getToken(), wxac.getEncodingAesKey());
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 解密消息
     * @param encryptedMsg
     * @param timestamp
     * @param nonce
     * @param msgSignature
     * @param appId
     * @param token
     * @param encodingAesKey
     * @return
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IOException
     * @throws AesException
     */
	private static String decryptMsg(String encryptedMsg, String timestamp, String nonce, String msgSignature,
			String appId, String token, String encodingAesKey)
			throws ParserConfigurationException, SAXException, IOException, AesException {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		StringReader sr = new StringReader(encryptedMsg);
		InputSource is = new InputSource(sr);
		Document document = db.parse(is);

		Element root = document.getDocumentElement();
		NodeList nodelist1 = root.getElementsByTagName("Encrypt");
		// NodeList nodelist2 = root.getElementsByTagName("MsgSignature");

		String encrypt = nodelist1.item(0).getTextContent();
		// String msgSignature = nodelist2.item(0).getTextContent();

		String fromXML = String.format(format, encrypt);
		
		if (encodingAesKey == null)
		    throw new IllegalStateException("encodingAesKey can not be null, config encodingAesKey first.");

		WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
		return pc.decryptMsg(msgSignature, timestamp, nonce, fromXML);    // 此处 timestamp 如果与加密前的不同则报签名不正确的异常
	}
	/**
	 * 加密消息
	 * @param msg
	 * @param timestamp
	 * @param nonce
	 * @param appId
	 * @param token
	 * @param encodingAesKey
	 * @return
	 * @throws AesException
	 */
	private static String encryptMsg(String msg, String timestamp, String nonce, String appId, String token,
			String encodingAesKey) throws AesException {
		WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
		return pc.encryptMsg(msg, timestamp, nonce);
	}
}



